/*
 * $Id: tcp.c 131 2011-10-12 08:31:42Z hyunghwan.chung $
 *
   Copyright 2005-2009 Chung, Hyung-Hwan.

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an  BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */

#include <xp/net/tcp.h>
#include <xp/bas/memory.h>
#include <xp/bas/utf8.h>
#include <xp/bas/assert.h>
#include <xp/bas/errno.h>

static xp_tcp_t* __tcp_open (
	xp_tcp_t* shell, xp_tcp_t* tcp, xp_sck_t* sck, int mode);
static xp_ssize_t __tcp_input (
	int cmd, void* arg, void* buf, xp_size_t size);
static xp_ssize_t __tcp_output (
	int cmd, void* arg, void* buf, xp_size_t size);

xp_tcp_t* xp_tcp_open (xp_tcp_t* tcp, int mode)
{
	return __tcp_open (XP_NULL, tcp, XP_NULL, mode);
}

xp_tcp_t* xp_tcp_openhnd (xp_tcp_t* tcp, int mode, int handle)
{
	xp_tcp_t* p;

	xp_sck_t* sck = xp_sck_openhnd (XP_NULL, handle);
	if (sck == XP_NULL) return XP_NULL;	

	p = __tcp_open (XP_NULL, tcp, sck, mode);
	if (p == XP_NULL) xp_sck_close (sck);	

	return p;
}

static xp_tcp_t* __tcp_open (
	xp_tcp_t* shell, xp_tcp_t* tcp, xp_sck_t* sck, int mode)
{
	if (tcp == XP_NULL) 
	{
		tcp = (xp_tcp_t*)xp_malloc (XP_SIZEOF(xp_tcp_t));
		if (tcp == XP_NULL) 
		{
			if (shell != XP_NULL) shell->errnum = XP_TCP_ENOMEM;
			return XP_NULL;
		}
		xp_memset (tcp, 0, XP_SIZEOF(*tcp));
		tcp->__dynamic = xp_true;
	}
	else 
	{
		xp_memset (tcp, 0, XP_SIZEOF(*tcp));
		tcp->__dynamic = xp_false;
	}

	if (mode == XP_TCP_TEXT) 
	{
		if (xp_tio_open(&tcp->tio) == XP_NULL) 
		{
			int num = xp_geterrno();

			if (shell != XP_NULL) shell->errnum = XP_TCP_ETIOOP;
			if (tcp->__dynamic) xp_free (tcp);

			xp_seterrno (num);
			return XP_NULL;
		}

		if (xp_tio_attinp(&tcp->tio, __tcp_input, tcp) == -1) 
		{
			int num = xp_geterrno();

			if (shell != XP_NULL) shell->errnum = XP_TCP_ETIOAI;
			xp_tio_close (&tcp->tio);
			if (tcp->__dynamic) xp_free (tcp);

			xp_seterrno (num);
			return XP_NULL;
		}
		if (xp_tio_attoutp(&tcp->tio, __tcp_output, tcp) == -1) 
		{
			int num = xp_geterrno();

			if (shell != XP_NULL) shell->errnum = XP_TCP_ETIOAO;
			xp_tio_close (&tcp->tio);
			if (tcp->__dynamic) xp_free (tcp);

			xp_seterrno (num);
			return XP_NULL;
		}
	}
	
	tcp->sck = sck;
	tcp->mode = mode;
	tcp->errnum = XP_TCP_ENOERR;
	return tcp;
}

int xp_tcp_close (xp_tcp_t* tcp)
{
	XP_ASSERT (tcp != XP_NULL);

	if (tcp->mode == XP_TCP_TEXT) {
		if (xp_tio_close (&tcp->tio) == -1) {
			tcp->errnum = XP_TCP_ETIOCL;
			return -1;
		}

		/* If xp_tcp_close fails somewhere below here, it might be
		 * called again explicitly for some reasons. but xp_tio_close 
		 * should not be called again. It is just a work-around to 
		 * change tcp->mode to XP_TCP_BINARY to prevent it from being
		 * called again.
		 */
		tcp->mode = XP_TCP_BINARY;
	}

	if (tcp->sck != XP_NULL) {
		if (xp_sck_close(tcp->sck) == -1) {
			tcp->errnum = XP_TCP_ESCKCL;
			return -1;
		}
		else tcp->sck = XP_NULL;
	}

	if (tcp->__dynamic) xp_free (tcp);
	return 0;
}

xp_sckhnd_t xp_tcp_gethnd (xp_tcp_t* tcp)
{
	XP_ASSERT (tcp != XP_NULL);
	return xp_sck_gethnd (tcp->sck);
}

int xp_tcp_geterrnum (xp_tcp_t* tcp)
{
	XP_ASSERT (tcp != XP_NULL);
	return tcp->errnum;
}

const xp_char_t* xp_tcp_geterrstr (xp_tcp_t* tcp)
{
	static const xp_char_t* __errstr[] =
	{
		XP_TEXT("no error"),
		XP_TEXT("out of memory"),
		XP_TEXT("invalid argument, data, etc"),
		XP_TEXT("operation timed out"),
		XP_TEXT("socket open failure"),
		XP_TEXT("socket close failure"),
		XP_TEXT("socket connect failure"),
		XP_TEXT("socket bind failure"),
		XP_TEXT("socket listen failure"),
		XP_TEXT("socket accept failure"),
		XP_TEXT("socket send failure"),
		XP_TEXT("socket receive failure"),
		XP_TEXT("tio open failure"),
		XP_TEXT("tio close failure"),
		XP_TEXT("tio input attach failure"),
		XP_TEXT("tio output attach failure"),
		XP_TEXT("not supported"),
		XP_TEXT("unknown error")
	};

	XP_ASSERT (tcp != XP_NULL);
	return __errstr[
		(tcp->errnum < 0 || tcp->errnum >= xp_countof(__errstr))?
		xp_countof(__errstr) - 1: tcp->errnum];
}

xp_time_t xp_tcp_gettmout (xp_tcp_t* tcp, int object)
{
	XP_ASSERT (tcp != XP_NULL);

	if (object & XP_TCP_CONN) return tcp->tmout.conn;
	if (object & XP_TCP_ACPT) return tcp->tmout.acpt;
	if (object & XP_TCP_SEND) return tcp->tmout.send;
	if (object & XP_TCP_RECV) return tcp->tmout.recv;

	return -1;
}

void xp_tcp_settmout (xp_tcp_t* tcp, int object, xp_time_t tmout)
{
	XP_ASSERT (tcp != XP_NULL);

	if (object & XP_TCP_CONN) tcp->tmout.conn = tmout;
	if (object & XP_TCP_ACPT) tcp->tmout.acpt = tmout;
	if (object & XP_TCP_SEND) tcp->tmout.send = tmout;
	if (object & XP_TCP_RECV) tcp->tmout.recv = tmout;

	if (tcp->sck) xp_sck_settmout (tcp->sck, object, tmout);
}

void xp_tcp_setcancel (xp_tcp_t* tcp, 
	int object, xp_bool_t (*cancel) (void*), void* arg)
{
	XP_ASSERT (tcp != XP_NULL);

	if (object & XP_TCP_CONN) 
	{
		tcp->cancel.conn = cancel;
		tcp->cancel.conn_arg = arg;
	}

	if (tcp->sck) xp_sck_setcancel (tcp->sck, object, cancel, arg);
}

int xp_tcp_conn (xp_tcp_t* tcp, xp_uint16_t port,
	const void* host, xp_size_t hostsz, xp_uint32_t scope_id)
{
	int n;
	xp_sckadrin4_t sin;
	xp_sckadrin6_t sin6;

	XP_ASSERT (tcp != XP_NULL);

	if (tcp->sck != XP_NULL) 
	{
		tcp->errnum = XP_TCP_EINVAL;
		return -1;
	}

	switch (hostsz) 
	{
	case XP_SIZEOF(sin.sin_addr.s_addr):
		if (XP_SIZEOF(sin.sin_addr.s_addr) != hostsz) 
		{
			tcp->errnum = XP_TCP_EINVAL;
			return -1;
		}

		tcp->sck = xp_sck_open (
			&tcp->__sck, AF_INET, SOCK_STREAM, 0);
		if (tcp->sck == XP_NULL) 
		{
			tcp->errnum = XP_TCP_ESCKOP;
			return -1;
		}

		xp_sck_settmout (tcp->sck, XP_SCK_CONN, tcp->tmout.conn);
		xp_sck_settmout (tcp->sck, XP_SCK_ACPT, tcp->tmout.acpt);
		xp_sck_settmout (tcp->sck, XP_SCK_SEND, tcp->tmout.send);
		xp_sck_settmout (tcp->sck, XP_SCK_RECV, tcp->tmout.recv);

		xp_sck_setcancel (tcp->sck, XP_SCK_CONN, tcp->cancel.conn, tcp->cancel.conn_arg);

		xp_memset (&sin, 0, XP_SIZEOF(sin));
		sin.sin_family = AF_INET;
		xp_memcpy (&sin.sin_addr.s_addr, host, hostsz);
		sin.sin_port = port;
		n = xp_sck_conn (
			tcp->sck, (xp_sckadr_t*)&sin, XP_SIZEOF(sin));
		break;

	case XP_SIZEOF(sin6.sin6_addr.s6_addr):
		if (XP_SIZEOF(sin6.sin6_addr.s6_addr) != hostsz) 
		{
			tcp->errnum = XP_TCP_EINVAL;
			return -1;
		}

		tcp->sck = xp_sck_open (
			&tcp->__sck, AF_INET6, SOCK_STREAM, 0);
		if (tcp->sck == XP_NULL) 
		{
			tcp->errnum = XP_TCP_ESCKOP;
			return -1;
		}

		xp_sck_settmout (tcp->sck, XP_SCK_CONN, tcp->tmout.conn);
		xp_sck_settmout (tcp->sck, XP_SCK_ACPT, tcp->tmout.acpt);
		xp_sck_settmout (tcp->sck, XP_SCK_SEND, tcp->tmout.send);
		xp_sck_settmout (tcp->sck, XP_SCK_RECV, tcp->tmout.recv);

		xp_memset (&sin6, 0, XP_SIZEOF(sin6));
		sin6.sin6_family = AF_INET6;
		sin6.sin6_port = port;
#ifdef XP_HAVE_IN6_SCOPE_ID
		sin6.sin6_scope_id = scope_id;
#endif
		xp_memcpy (&sin6.sin6_addr.s6_addr, host, hostsz);
		n = xp_sck_conn (
			tcp->sck, (xp_sckadr_t*)&sin6, XP_SIZEOF(sin6));
		break;

	default:
		tcp->errnum = XP_TCP_EINVAL;
		return -1;
	}

	if (n == -1) 
	{
		int num = xp_geterrno();

		tcp->errnum = (num == XP_ETIMEDOUT)? 
			XP_TCP_ETMOUT: XP_TCP_ESCKCN;

		xp_sck_close (tcp->sck);
		tcp->sck = XP_NULL;	

		xp_seterrno (num);
		return -1;
	}

	return 0;
}

int xp_tcp_bind (xp_tcp_t* tcp, xp_uint16_t port,
	const void* host, xp_size_t hostsz, xp_uint32_t scope_id)
{
	int n;
	xp_sckadrin4_t sin;
	xp_sckadrin6_t sin6;

	XP_ASSERT (tcp != XP_NULL);

	if (tcp->sck != XP_NULL) {
		tcp->errnum = XP_TCP_EINVAL;
		return -1;
	}

	switch (hostsz) {
	case XP_SIZEOF(sin.sin_addr.s_addr):
		if (XP_SIZEOF(sin.sin_addr.s_addr) != hostsz) {
			tcp->errnum = XP_TCP_EINVAL;
			return -1;
		}

		tcp->sck = xp_sck_open (
			&tcp->__sck, AF_INET, SOCK_STREAM, 0);
		if (tcp->sck == XP_NULL) {
			tcp->errnum = XP_TCP_ESCKOP;
			return -1;
		}

		n = 1;
		xp_sck_setopt (tcp->sck,
			SOL_SOCKET, SO_REUSEADDR, &n, XP_SIZEOF(n));

		xp_sck_settmout (tcp->sck, XP_SCK_CONN, tcp->tmout.conn);
		xp_sck_settmout (tcp->sck, XP_SCK_ACPT, tcp->tmout.acpt);
		xp_sck_settmout (tcp->sck, XP_SCK_SEND, tcp->tmout.send);
		xp_sck_settmout (tcp->sck, XP_SCK_RECV, tcp->tmout.recv);

		xp_memset (&sin, 0, XP_SIZEOF(sin));
		sin.sin_family = AF_INET;
		sin.sin_port = port;
		xp_memcpy (&sin.sin_addr.s_addr, host, hostsz);
		n = xp_sck_bind (
			tcp->sck, (xp_sckadr_t*)&sin, XP_SIZEOF(sin));
		break;

	case XP_SIZEOF(sin6.sin6_addr.s6_addr):
		if (XP_SIZEOF(sin6.sin6_addr.s6_addr) != hostsz) {
			tcp->errnum = XP_TCP_EINVAL;
			return -1;
		}

		tcp->sck = xp_sck_open (
			&tcp->__sck, AF_INET6, SOCK_STREAM, 0);
		if (tcp->sck == XP_NULL) {
			tcp->errnum = XP_TCP_ESCKOP;
			return -1;
		}

		n = 1;
		xp_sck_setopt (tcp->sck, 
			SOL_SOCKET, SO_REUSEADDR, &n, XP_SIZEOF(n));

		xp_sck_settmout (tcp->sck, XP_SCK_CONN, tcp->tmout.conn);
		xp_sck_settmout (tcp->sck, XP_SCK_ACPT, tcp->tmout.acpt);
		xp_sck_settmout (tcp->sck, XP_SCK_SEND, tcp->tmout.send);
		xp_sck_settmout (tcp->sck, XP_SCK_RECV, tcp->tmout.recv);

		xp_memset (&sin6, 0, XP_SIZEOF(sin6));
		sin6.sin6_family = AF_INET6;
		sin6.sin6_port = port;
#ifdef XP_HAVE_IN6_SCOPE_ID
		sin6.sin6_scope_id = scope_id;
#endif
		xp_memcpy (&sin6.sin6_addr.s6_addr, host, hostsz);
		n = xp_sck_bind (
			tcp->sck, (xp_sckadr_t*)&sin6, XP_SIZEOF(sin6));
		break;

	default:
		tcp->errnum = XP_TCP_EINVAL;
		return -1;
	}

	if (n == -1) {
		int num = xp_geterrno();

		tcp->errnum = (num == XP_ETIMEDOUT)? 
			XP_TCP_ETMOUT: XP_TCP_ESCKBN;

		xp_sck_close (tcp->sck);
		tcp->sck = XP_NULL;	

		xp_seterrno (num);
		return -1;
	}
	
#ifdef IPV6_V6ONLY
	{
		int n = 1;
		if (hostsz == XP_SIZEOF(sin6.sin6_addr.s6_addr)) {
			xp_sck_setopt (tcp->sck, 
				IPPROTO_IPV6, IPV6_V6ONLY, &n, XP_SIZEOF(n));
		}
	}
#endif

	return 0;
}

int xp_tcp_lstn (xp_tcp_t* tcp, int backlog)
{
	XP_ASSERT (tcp != XP_NULL);

	if (tcp->sck == XP_NULL) {
		tcp->errnum = XP_TCP_EINVAL;
		return -1;
	}

	if (xp_sck_lstn (tcp->sck, backlog) == -1) {
		tcp->errnum = XP_TCP_ESCKLS;
		return -1;
	}

	return 0;
}

xp_tcp_t* xp_tcp_acpt (xp_tcp_t* tcp, xp_tcp_t* client)
{
	xp_sckadrin4_t sin;
	xp_sckadrin6_t sin6;
	xp_sckadr_t* adr;
	xp_scklen_t len;
	xp_sck_t* newsck;

	XP_ASSERT (tcp != XP_NULL);

	if (tcp->sck == XP_NULL) {
		tcp->errnum = XP_TCP_EINVAL;
		return XP_NULL;
	}

	if (XP_SIZEOF(sin6) > XP_SIZEOF(sin)) {
		adr = (xp_sckadr_t*)&sin6;
		len = XP_SIZEOF(sin6);
	}
	else {
		adr = (xp_sckadr_t*)&sin;
		len = XP_SIZEOF(sin);
	}

	if (client == XP_NULL) {
		newsck = xp_sck_acpt (tcp->sck, XP_NULL, adr, &len);
	}
	else {
		newsck = xp_sck_acpt (tcp->sck, &client->__sck, adr, &len);
	}

	if (newsck == XP_NULL) {
		tcp->errnum = (xp_geterrno() == XP_ETIMEDOUT)? 
			XP_TCP_ETMOUT: XP_TCP_ESCKAC;
		return XP_NULL;
	}

	return __tcp_open (tcp, client, newsck, tcp->mode);
}

xp_ssize_t xp_tcp_send (xp_tcp_t* tcp, const void* data, xp_size_t size)
{
	xp_ssize_t n;

	XP_ASSERT (tcp != XP_NULL);

	if (tcp->sck == XP_NULL) {
		tcp->errnum = XP_TCP_EINVAL;
		return -1;
	}

	if (tcp->mode == XP_TCP_BINARY) {
		n = xp_sck_send (tcp->sck, data, size, 0);
	}
	else {
		XP_ASSERT (tcp->mode == XP_TCP_TEXT);
		n = xp_tio_putsx(&tcp->tio, (const xp_char_t*)data, size);
		if (xp_tio_flush(&tcp->tio) == -1) n = -1;
	}

	if (n == -1) {
		tcp->errnum = (xp_geterrno() == XP_ETIMEDOUT)? 
			XP_TCP_ETMOUT: XP_TCP_ESCKSN;
		return -1;
	}

	return n;
}

xp_ssize_t xp_tcp_recv (xp_tcp_t* tcp, void* buf, xp_size_t size)
{
	xp_ssize_t n;

	XP_ASSERT (tcp != XP_NULL);

	if (tcp->sck == XP_NULL) {
		tcp->errnum = XP_TCP_EINVAL;
		return -1;
	}

	if (tcp->mode == XP_TCP_BINARY) {
		n = xp_sck_recv (tcp->sck, buf, size, 0);
	}
	else {
		XP_ASSERT (tcp->mode == XP_TCP_TEXT);
		n = xp_tio_getsx (&tcp->tio, (xp_char_t*)buf, size);
	}

	if (n == -1) {
		tcp->errnum = (xp_geterrno() == XP_ETIMEDOUT)? 
			XP_TCP_ETMOUT: XP_TCP_ESCKRC;
		return -1;
	}

	return n;
}

xp_ssize_t xp_tcp_recvstr (xp_tcp_t* tcp, xp_str_t* buf)
{
	xp_ssize_t n;

	XP_ASSERT (tcp != XP_NULL);

	if (tcp->sck == XP_NULL)
	{
		tcp->errnum = XP_TCP_EINVAL;
		return -1;
	}

	if (tcp->mode == XP_TCP_BINARY)
	{
		tcp->errnum = XP_TCP_UNSUPP;
		return -1;
	}
	else
	{
		XP_ASSERT (tcp->mode == XP_TCP_TEXT);
		n = xp_tio_getstr (&tcp->tio, buf);
		if (n == -1)
		{
			tcp->errnum = (xp_geterrno() == XP_ETIMEDOUT)?
				XP_TCP_ETMOUT: XP_TCP_ESCKRC;
			return -1;
		}
		return n;
	}
}

static xp_ssize_t __tcp_input (int cmd, void* arg, void* buf, xp_size_t size)
{
	xp_tcp_t* tcp = (xp_tcp_t*)arg;

	XP_ASSERT (tcp != XP_NULL);
	if (cmd == XP_TIO_IO_DATA) {
		XP_ASSERT (tcp->sck != XP_NULL);
		return xp_sck_recv (tcp->sck, buf, size, 0);
	}

	return 0;
}

static xp_ssize_t __tcp_output (int cmd, void* arg, void* buf, xp_size_t size)
{
	xp_tcp_t* tcp = (xp_tcp_t*)arg;

	XP_ASSERT (tcp != XP_NULL);
	if (cmd == XP_TIO_IO_DATA) {
		XP_ASSERT (tcp->sck != XP_NULL);
		return xp_sck_send (tcp->sck, buf, size, 0);
	}

	return 0;
}

